#pragma once
// 逻辑控制模块(为路由模块提供服务)

#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <assert.h>
#include <fstream>
#include<algorithm>
#include <jsoncpp/json/json.h>

#include "oj_model_mysql.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/httplib.h"
#include "../comm/util.hpp"

namespace ns_control
{
    using namespace std;
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_log;
    using namespace ns_util;
    using namespace httplib;

    // 提供服务的主机
    class Machine
    {
    public:
        string _ip;     // 编译服务主机ip
        int _port;      // 端口号
        uint64_t _load; // 计数器（记录当前请求数量）
        mutex *_mtx;    // 保护多线程安全
    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mtx(nullptr)
        {
        }

        ~Machine()
        {
        }
    public:

        //更新主机负载（主机被使用：负载++）
        void IncLoad()
        {
            if(_mtx) _mtx->lock();
            _load++;
            if(_mtx) _mtx->unlock();
        }

        //更新主机负载（主机被离线：负载--）
        void DecLoad()
        {
            if(_mtx) _mtx->lock();
            _load--;
            if(_mtx) _mtx->unlock();
        }

        //进行离线前，要清空本主机的负载，避免影响后续重新上线后的负载情况
        void ResetLoad()
        {
            if(_mtx) _mtx->lock();
            _load = 0;
            if(_mtx) _mtx->unlock();
        }

        //获取主机负载
        uint64_t Load()
        {
            uint64_t load = 0;
            if(_mtx) _mtx->lock();
            load = _load;
            if(_mtx) _mtx->unlock();
            return load;
        }

    };

    // service_machine.conf文件路径信息
    const string service_machine = "./conf/service_machine.conf";

    // 负载均衡模块
    class LoadBlance
    {
    private:
        vector<Machine> _machines; // 存放所有可用主机信息(用下标充当主机ip)
        vector<int> _online;       // 所有在线的主机id(下标)
        vector<int> _offline;      // 所有离线的主机id
        mutex _mtx;
    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine)); // 获取所有主机信息
            LOG(INFO) << "加载：" << service_machine << "成功！"
                      << "\n";
        }
        ~LoadBlance()
        {
        }

    public:
        // 将service_machine.conf中的主机信息加载进_machines中
        bool LoadConf(const string &machine_conf)
        {
            ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL) << "加载：" << machine_conf << "失败！"
                           << "\n";
                return false;
            }

            string line;
            while (getline(in, line)) // 按行读取主机信息
            {
                vector<string> tokens; // 存放切分好的信息
                StringUtil::SplitString(line, &tokens, ":");

                if (tokens.size() != 2)
                {
                    LOG(WARNING) << "切分：" << line << "失败！"
                                 << "\n";
                    continue;
                }

                Machine m;                         // 填充主机信息，构成主机对象
                m._ip = tokens[0];                 // ip
                m._port = atoi(tokens[1].c_str()); // 端口号
                m._load = 0;                       // 负载
                m._mtx = new mutex();              // 每一台主机都带有锁

                _online.push_back(_machines.size()); // 使用下标作为id（在线主机）
                _machines.push_back(m);
            }

            in.close();
            return true;
        }

        // 智能选择一台主机对外提供服务（需要注意线程安全）负载均衡
        bool SmartChoice(int* id, Machine** m)//id 主机对象
        {
            //规则：当主机第一次被纳入配置文件中时，其都为在线主机
            _mtx.lock();//线程安全

            //1.使用选择好的主机前应该先：更新主机负载
            //负载均衡算法：轮询+哈希
            int online_num = _online.size();//当前在线的主机数
            if(online_num == 0)//当前没有在线的主机，不可用->获取失败
            {
                _mtx.unlock();//解锁
                LOG(FATAL)<<"所有后端编译主机已经离线，请运维同事尽快处理！"<<"\n";
                return false;
            }

            //通过遍历的方式找到在线主机的最小负载的机器与负载数(轮询+哈希)
            //此时最少有一台主机在线
            *id = _online[0];
            *m = &_machines[_online[0]];
            uint64_t min_load = _machines[_online[0]].Load();//所有主机中在线主机的负载数
            for(int i = 1; i<online_num; i++)
            {
                uint64_t curr_load = _machines[_online[i]].Load();
                if(min_load > curr_load)
                {
                    min_load = curr_load;
                    *id = _online[i];//更新主机id
                    *m = &_machines[_online[i]];//拿到对应主机对象（二级指针，可供外面修改）
                }
            }

            _mtx.unlock();
            return true;
        }

        // 离线问题
        void OfflineMachine(int which)
        {
            _mtx.lock();
            for(auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if(*iter == which)
                {
                    _machines[which].ResetLoad();//清空该主机内部的负载
                    //找到要离线的主机
                    //离线操作
                    _online.erase(iter);//注意迭代器失效问题
                    _offline.push_back(which);
                    break;
                }
            }
            _mtx.unlock();
        }

        // 在线问题
        void OnlineMachine()
        {
            //规定：当所有主机都下线时，将全部主机上线
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());//将离线主机插入到在线主机
            _offline.erase(_offline.begin(), _offline.end());//清空离线主机
            _mtx.unlock();

            LOG(INFO)<<"所有主机已重新上线！"<<"\n";
        }

        //for test
        void ShowMachines()
        {
            _mtx.lock();

            cout<<"当前在线主机列表： ";
            for(auto& iter : _online)
            {
                cout<<iter<<" ";
            }
            cout<<endl;

            cout<<"当前离线主机列表： ";
            for(auto& iter : _offline)
            {
                cout<<iter<<" ";
            }
            cout<<endl;
            
            _mtx.unlock();
        }

    };

    // 业务逻辑控制器
    class Control
    {
    private:
        Model model_; // 提供题目数据
        View view_;   // 提供网页渲染功能
        LoadBlance load_blance_;//负载均衡器
    public:
        Control()
        {
        }
        ~Control()
        {
        }

    public:

        //上线主机
        void RecoveryMachine()
        {
            load_blance_.OnlineMachine();
        }

        // 获取完整的题库页面
        bool AllQuestions(string *html)
        {
            bool ret = true;
            vector<struct Question> all; // 全部题目信息
            if (model_.GetAllQuestions(&all))
            {
                sort(all.begin(),all.end(),[](const struct Question& q1, const struct Question& q2){
                    return atoi(q1.number.c_str()) < atoi(q2.number.c_str());//运用题目编号进行页面题目升序排序
                });
                // 获取题目信息成功，并要将题目信息构建成网页
                view_.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败，形成题目列表失败";
                ret = false;
            }
            return ret;
        }

        // 根据题目编号 获取指定题目页面
        bool Question(const string &number, string *html)
        {
            bool ret = true;
            struct Question q;
            if (model_.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功，并要将题目信息构建成网页
                view_.OneExpandHtml(q, html);
            }
            else
            {
                *html = "指定题目：" + number + "不存在！";
                ret = false;
            }
            return ret;
        }

        // 判题功能
        void Judge(const string& number, const std::string in_json, string *out_json)
        {
            //根据题号拿到题目
            struct Question q;
            model_.GetOneQuestion(number, &q);

            // 1.对in_json进行反序列化，提取用户代码信息
            Json::Value in_value;
            Json::Reader read;
            read.parse(in_json, in_value);

            // 2.拼接用户代码与测试用例代码，形成新的代码
            string code = in_value["code"].asString();

            Json::Value compile_value;//返回值，用于序列化
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + " " + q.tail;//用户提交代码与测试用例结合
            compile_value["cup_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;

            //输出结果反序列化
            Json::FastWriter writer;
            string compile_string = writer.write(compile_value);//拼接后的用户请求

            // 3.选择负载最低的主机
            while(true)
            {
                int id = 0;
                Machine* m = nullptr;
                if(!load_blance_.SmartChoice(&id, &m))//负载均衡的选择主机
                {
                    break;
                }

                // 4.发起HTTP请求，得到结果(必须是客户端发起请求)->请求编译服务器处理
                Client cli(m->_ip, m->_port);

                m->IncLoad();//获取成功，增加负载数

                LOG(INFO)<<"选择主机成功，主机ID："<<id<<" 详情："<<m->_ip<<":"<<m->_port<<"当前主机的负载是："<<m->Load()<<"\n";

                //检测运行返回值是否正确(成功不为空，  失败为空)->调用编译运行服务
                if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))//成功返回结果，失败返回空
                {
                    // 5.将结果赋值给out_json，返回给用户
                    //请求成功(输出信息在文件中)
                    if(res->status == 200)//状态码为200才表示请求成功
                    {
                        *out_json = res->body;
                        m->DecLoad();//使用完毕，减少负载
                        LOG(INFO)<<"请求编译运行服务成功..."<<"\n";
                        break;
                    }
                    m->DecLoad();//选择了主机，但返回状态码不正确
                }
                else
                {
                    //请求失败
                    LOG(Error)<<"请求主机失败，主机ID："<<id<<" 详情："<<m->_ip<<":"<<m->_port<<"可能已经离线!"<<"\n";
                    load_blance_.OfflineMachine(id);//离线:当智能选择到了主机，但该主机被请求后没有返回结果，所以要被离线
                    load_blance_.ShowMachines();//调试
                }
            }
        }
    };
}
